/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.log4j.Logger;

import be.destin.rdf.changes.Status;
import be.destin.skos.search.CountCacher;
import be.destin.skos.search.LabelMatchType;
import be.destin.skos.search.SearchResult;
import be.destin.util.Util;

public class DynamicScheme extends ConceptScheme {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/** log4j category */
	private static Logger log = Logger.getLogger(DynamicScheme.class);

	private static final float hashTableLoadFactor = 0.75f;
	private static final char sqlJoker = '%';

	protected int schemeSize = 0;
	protected int cacheSize = 0;
	
	private LinkedHashMap<String/* handle */, Concept> entries = null;

	private String[] allSQL = null;
	private String[] findSQL = null;
	private String[] namesSQL = null;
	private String[] notesSQL = null;
	private String[] aliassesSQL = null;
	private String[] synonymsSQL = null;
	private String[] parentsSQL = null;
	private String[] childrenSQL = null;
	private String[] broadMatchSQL = null;
	private String[] narrowMatchSQL = null;
	private String[] relatedSQL = null;
	private String[] relatedMatchSQL = null;
	private String[] exactMatchSQL = null;
	private String[] closeMatchSQL = null;
	private String[] matchSQL = null;
	private boolean matchSQLfull = false;
	private HashMap<String,String[]> indexSQL = null;
	private HashMap<String,String[]> indexSearchSQL = null;
	private transient SqlPool poolContext = null;
	
	//private ConcurrentHashMap<String, ReferringApplicationList> countCache = new ConcurrentHashMap<String,ReferringApplicationList>(); 
	
	public DynamicScheme() {
		super();
	}

	/**
	 * @param code
	 */
	public DynamicScheme(SkosManager skosManager,String code) {
		super(skosManager, code);
	}

	public DynamicScheme(SkosManager skosManager, String code, int aCacheSize) {
		super(skosManager, code);
		this.setCacheSize(aCacheSize);
	}

	@XmlTransient
	public boolean isDynamic() {
		return true;
	}
	/*
	 * STUPID duplication made mandatory by XmlBinding...
	 */
	/**
	 * @return the displays
	 */
	@XmlElement
	public UrlList getDisplay() {
		return super.getDisplay();
	}

	@XmlElement
	public UrlList getCreate() {
		return creates;
	}

	/**
	 * @return the internalNote
	 */
	@XmlElement
	public String getInternalNote() {
		return super.getInternalNote();
	}

	/**
	 * @return the helps
	 */
	@XmlElement
	public UrlList getHelp() {
		return helps;
	}

	/**
	 * @return the icons
	 */
	@XmlElement
	public UrlList getIcon() {
		return super.getIcon();
	}

	/**
	 * @return the titles
	 */
	@XmlElement
	public TermList getTitle() {
		return super.getTitle();
	}

	/**
	 * @return the descriptions
	 */
	@XmlElement
	public TermList getDescription() {
		return super.getDescription();
	}

	/**
	 * @return the namespace
	 */
	@XmlElement(name="namespace")
	public String getNamespace() {
		return namespace;
	}

	/**
	 * @return the namespace
	 */
	@XmlElement(name="uri")
	public String getUri() {
		return uri;
	}

	/*
	 * END of DUPLICATION
	 */
	
	private String[] getSqlSource(Properties properties, String param) {
		return getSqlSource(properties, param, false);
	}
	private String[] getSqlSource(Properties properties, String param, boolean mandatory) {
		String source = properties.getProperty(param); // SQL to get the list of all concepts
		if (source == null || source.isEmpty()) {
			if (mandatory) log.error("SQL statement for "+param+"= is missing");
			return null;
		} else {
			String[] sourceSQL = SqlSource.splitSQL(source);
			if (sourceSQL == null) return null;
			for (int i=0; i<sourceSQL.length; i++) {
				sourceSQL[i] = poolContext.substitute(this,sourceSQL[i]);
				log.debug(param+'['+i+"]="+sourceSQL[i]);
			}
			return sourceSQL;
		}
	}

	protected void initSQL(Properties properties, String configFile,
			Map<String, String> notationsDefinitions,
			Map<String, String> notationsSearchDefinitions) {

		String dbPool = properties.getProperty("pool");
		Connection con = SqlPool.getSqlConnection(configFile,dbPool);

		poolContext = SqlPool.getSqlPool(dbPool);

		allSQL = getSqlSource(properties,"all",true);
		findSQL = getSqlSource(properties,"about");

		// names of
		// the authority concepts
		// languages are deducted from the list of the languages of the list
		// names
		// code, name-fr, name-en...,
		namesSQL = getSqlSource(properties,SqlSource.namesTable); // SQL to get the labels
		notesSQL = getSqlSource(properties,SqlSource.notesTable); // SQL to get the notes

		// SQL to get the aliasses of the authority concepts codes
		aliassesSQL = getSqlSource(properties,SqlSource.aliassesTable);

		// code, language, synonym
		synonymsSQL = getSqlSource(properties,SqlSource.altTable);

		// code, broader
		parentsSQL = getSqlSource(properties,SqlSource.parentsTable);

		// code, narrower
		childrenSQL = getSqlSource(properties,SqlSource.childrenTable);

		relatedSQL = getSqlSource(properties,SqlSource.relatedTable);

		broadMatchSQL = getSqlSource(properties,SqlSource.broadMatchTable); // SQL to get broader concepts (other scheme)

		narrowMatchSQL = getSqlSource(properties,SqlSource.narrowMatchTable);

		relatedMatchSQL = getSqlSource(properties,SqlSource.relatedMatchTable);

		exactMatchSQL = getSqlSource(properties,SqlSource.exactMatchTable);

		closeMatchSQL = getSqlSource(properties,SqlSource.closeMatchTable);

		matchSQL = getSqlSource(properties,"match"); // SQL to get matching concepts for string (label search)
		matchSQLfull = properties.getProperty("matchfull","no").toLowerCase().startsWith("y"); // Full text MATCH is not like ILIKE ...

		indexSQL = new HashMap<String,String[]>();
		indexSearchSQL = new HashMap<String,String[]>();
		for (Entry<String,String> indexEntry : notationsDefinitions.entrySet()) {
			String[] someSQL = getSqlSource(properties,indexEntry.getValue());
			if (someSQL != null) {
				indexSQL.put(indexEntry.getKey(),someSQL);
			}
			someSQL = getSqlSource(properties,notationsSearchDefinitions.get(indexEntry.getKey()));
			if (indexSearchSQL != null) {
				indexSearchSQL.put(indexEntry.getKey(),someSQL);
			}
		}

		String code = this.getAbout();
		// the
		// parent(s) of the
		// authority concepts
		String[] sizeSQLs = getSqlSource(properties,"size"); // SQL to get the size of the Scheme
		int totalSizeScheme = 0;
		if (sizeSQLs != null) {
			for (String sizeSQL: sizeSQLs) {
				Statement sizeStmt = null;
				ResultSet rs = null;
				try {
					// create and execute a SELECT
					sizeStmt = con.createStatement();
					rs = sizeStmt.executeQuery(sizeSQL);

					if (rs.next()) {
						int sizeScheme = rs.getInt("size");
						if (sizeScheme == 0) {
							String message = code
									+ ": size field is not returned or table is empty. SQL query for size="
									+ sizeSQL;
							log.error(message);
							this.getErrors().add(message);
						} else {
							totalSizeScheme += sizeScheme;
						}
						log.debug("Result size="+sizeScheme);
					}
				} catch (SQLException ex) {
					String message = code + ", SQL=" + sizeSQL + " : " + ex.toString();
					log.error(message);
					this.getErrors().add(message);
				}
				if (rs != null) {
					try {
						rs.close();
					} catch (SQLException ex) {
						String message = code + ", close result set for SQL=" + sizeSQL + " : " + ex.toString();
						log.error(message);
						this.getErrors().add(message);
					}
				}
				if (sizeStmt != null) {
					try {
						sizeStmt.close();
					} catch (SQLException ex) {
						String message = code + ", close statement for SQL=" + sizeSQL + " : " + ex.toString();
						log.error(message);
						this.getErrors().add(message);
					}
				}
			}
			this.setSize(totalSizeScheme);
		}
		SqlPool.freeConnection(con);
	}

private void readLabels (ResultSet rs, Concept aConcept) {
	// get current row values
	String currLang = TermList.anyLanguage;
	String currSource = null;
	Status currStatus = null;
	TermFormat currFormat = null;
	try {
		String aStatus = rs.getString("status");
		if (aStatus != null && aStatus.length() > 0) {
			// log.debug(nameLang+"="+aName);
			currStatus = Status.valueOf(aStatus);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	try {
		String aFormat = rs.getString("format");
		if (aFormat != null && aFormat.length() > 0) {
			// log.debug(nameLang+"="+aName);
			currFormat = TermFormat.valueOf(aFormat);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	String relQualifier = null;
	try {
		relQualifier = rs.getString("qual");
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	try {
		String aHandle = rs.getString("broader");
		if (aHandle != null && aHandle.length() > 0) {
			// log.debug(nameLang+"="+aName);
			aConcept.putBroader(aHandle, currStatus, relQualifier);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	// get current row values
	try {
		String aHandle = rs.getString("broadmatch");
		if (aHandle != null && aHandle.length() > 0) {
			// log.debug(nameLang+"="+aName);
			aConcept.putBroadMatch(aHandle, currStatus, relQualifier);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	// get current row values
	try {
		String aLang = rs.getString("lang");
		if (aLang != null && aLang.length() > 0) {
			// log.debug(nameLang+"="+aName);
			currLang = aLang;
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	// get current row values
	try {
		currSource = rs.getString("source");
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	// get current row values
	try {
		String aName = rs.getString("label");
		if (aName != null && aName.length() > 0) {
			// log.debug(nameLang+"="+aName);
			aConcept.putPrefLabel(currLang, currFormat, aName, currSource, currStatus);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	// get current row values
	try {
		String aNote = rs.getString("note");
		if (aNote != null && aNote.length() > 0) {
			// log.debug(nameLang+"="+aName);
			aConcept.putScopeNote(currLang, currFormat, aNote, currStatus);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	// get current row values
	try {
		String anIcon = rs.getString(SchemeFactory.iconRoot);
		if (anIcon != null && anIcon.length() > 0) {
			// log.debug(nameLang+"="+aName);
			aConcept.putIcon(currLang, anIcon);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	try {
		String anIcon = rs.getString("url");
		if (anIcon != null && anIcon.length() > 0) {
			// log.debug(nameLang+"="+aName);
			aConcept.putUrl(currLang, anIcon);
		}
	} catch (java.sql.SQLException ex) { // a column may
		// be missing
		// for a given
		// language
	}
	for (Term nameLang : this.getTitle()) {
		try {
			TermFormat currLFormat = currFormat;
			Status currLStatus = currStatus;
			String currLSource = currSource;
			String aStatus = rs.getString("status-" + nameLang.getLang());
			if (aStatus != null && aStatus.length() > 0) {
				// log.debug(nameLang+"="+aName);
				currLStatus = Status.valueOf(aStatus);
			}
			String aFormat = rs.getString("format-" + nameLang.getLang());
			if (aFormat != null && aFormat.length() > 0) {
				// log.debug(nameLang+"="+aName);
				currLFormat = TermFormat.valueOf(aFormat);
			}
			String aSource = rs.getString("source-" + nameLang.getLang());
			if (aSource != null && aSource.length() > 0) {
				// log.debug(nameLang+"="+aName);
				currLSource = aSource;
			}
			String aName = rs.getString("label-" + nameLang.getLang());
			if (aName != null && aName.length() > 0) {
				// log.debug(nameLang+"="+aName);
				aConcept.putPrefLabel(nameLang.getLang(), currLFormat, aName, currLSource, currLStatus);
			}
			String aNote = rs.getString("note-" + nameLang.getLang());
			if (aNote != null && aNote.length() > 0) {
				// log.debug(nameLang+"="+aName);
				aConcept.putScopeNote(nameLang.getLang(), currLFormat, aNote, currLStatus);
			}
			String anIcon = rs.getString(SchemeFactory.iconRoot+"-" + nameLang.getLang());
			if (anIcon != null && anIcon.length() > 0) {
				// log.debug(nameLang+"="+aName);
				aConcept.putIcon(nameLang.getLang(), anIcon);
			}
			String anUrl = rs.getString("url-" + nameLang.getLang());
			if (anIcon != null && anIcon.length() > 0) {
				// log.debug(nameLang+"="+aName);
				aConcept.putUrl(nameLang.getLang(), anUrl);
			}
		} catch (java.sql.SQLException ex) { // a column may
			// be missing
			// for a given
			// language
		}
	}
}


private Concept readSQL(Connection con, String handle, boolean everything) {
	Concept aConcept = new Concept(this);
	String internalID = handle;
	String currCode = handle;
	SkosManager skosManager = this.getManagerInstance();
	if (findSQL != null && skosManager != null) {
		String code = this.getAbout();
		for (String aFindSQL: findSQL) {
			PreparedStatement aStmt = SqlSource.prepareQueryDB(con,aFindSQL);
			if (aStmt != null) {
				try {
					// create and execute a SELECT

					ResultSet rs = SqlSource.queryDB(aFindSQL, aStmt,handle);
					if (rs != null) {
						boolean something = false;
						while (rs.next()) {
							something = true;
							currCode = SqlSource.getAbout(this,rs,"about", true);
							if (currCode == null || currCode.length() == 0) {
								String message = handle + ": about field is not returned by SQL query to find a concept";
								log.error(message);
								this.getErrors().add(message);
							}
							else {
								internalID = currCode;
							}

							try {
								String currId = rs.getString("id"); // Because it may ease the next step to use an internal ID
								if (currId != null && currId.length() > 0) {
									internalID = currId;
								}
							} catch (SQLException e) { }
							readLabels(rs,aConcept); // Labels may be available with the find
							log.debug(code + ", read about=" + handle + ", id="+internalID);
						}
						if (!something) internalID = null;
						rs.close();
						//findStmt.close();
					} else internalID = null;
					if (aStmt != null) {
						try {
							aStmt.close();
						} catch (SQLException ex) {
							log.error(ex.toString()+", closing statement set, SQL="+findSQL);
							Util.printStack(ex);
						} 
					}
				} catch (Exception ex) {
					String message = code + ", about=" + handle + " : " + ex.toString();
					log.error(message);
					this.getErrors().add(message);
					Util.printStack(ex);
					internalID = null;
				}
			}
		}
	}
	if (internalID == null) {
		return null;
	} else {
		Concept newConcept = this.putConcept(currCode, aConcept);
		newConcept.setComplete(everything);
		oneConceptSQL(con, newConcept, currCode, internalID, everything);
		return newConcept;
	}
}

private void readRelations(Connection con, Concept aConcept, String internalID, String[] relSQL,
							boolean local, String column, Method putMatch) {
	String code = this.getAbout();
	if (relSQL != null) {
		String currSchemePrefix = code+SchemeInterface.separator;
		for (String aRelSQL : relSQL) {
			PreparedStatement aStmt = SqlSource.prepareQueryDB(con,aRelSQL);
			if (aStmt != null) {
				try {
					ResultSet rs = SqlSource.queryDB(aRelSQL, aStmt,internalID);
					if (rs != null) {
						while (rs.next()) {
							String currParent = rs.getString(column);
							if (currParent == null) {
								String message = code + ": "+column+" field is not returned by SQL query";
								log.error(message);
								this.getErrors().add(message);
								break;
							}

							if (local) {
								if (currParent.startsWith(currSchemePrefix)) {
									currParent = currParent.substring(currSchemePrefix.length());
									if (currParent.isEmpty()) continue;
								}
							} else {
								if (currParent.indexOf(SchemeInterface.separator) < 0) {
									String message = aConcept.getScheme_About() + ": "+column+" \""+currParent+"\"is not prefixed with scheme code?";
									log.error(message);
									aConcept.addError(message);
									continue;
								}
							}

							Status currStatus = null;
							try {
								String aStatus = rs.getString("status");
								if (aStatus != null && aStatus.length() > 0) {
									// log.debug(nameLang+"="+aName);
									currStatus = Status.valueOf(aStatus);
								}
							} catch (java.sql.SQLException ex) {
							}
							String relQualifier = null;
							try {
								relQualifier = rs.getString("qual");
							} catch (java.sql.SQLException ex) { 
							}

							try {
								putMatch.invoke(aConcept, currParent, currStatus, relQualifier);
							} catch (IllegalArgumentException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							} catch (IllegalAccessException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							} catch (InvocationTargetException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}

						}
						rs.close();
					}
					//parentsStmt.close();
					if (aStmt != null) {
						try {
							aStmt.close();
						} catch (SQLException ex) {
							log.error(ex.toString()+", closing statement set, SQL="+aRelSQL);
							Util.printStack(ex);
						} 
					}
				} catch (SQLException ex) {
					String message = code + ", "+column+": " + ex.toString();
					log.error(message);
					this.getErrors().add(message);
					Util.printStack(ex);
				}
			}
		}
	}
}

private void oneConceptSQL(Connection con, Concept aConcept, String currCode, String internalID, boolean everything) {

	SkosManager skosManager = this.getManagerInstance();
	String code = this.getAbout();
	if (namesSQL != null) {
		for (String aNameSQL: namesSQL) {
			try {
				// create and execute a SELECT
				PreparedStatement aStmt = SqlSource.prepareQueryDB(con,aNameSQL);
				ResultSet rs = SqlSource.queryDB(aNameSQL, aStmt,internalID);
				if (rs != null) {
					while (rs.next()) {
						readLabels(rs,aConcept); // Labels may be available with the find
					}
					log.debug(code + ", namesSQL=" + internalID + ":"+aConcept.getPrefLabel("", false));
					rs.close();
					//namesStmt.close();
				}
				if (aStmt != null) {
					try {
						aStmt.close();
					} catch (SQLException ex) {
						log.error(ex.toString()+", closing statement set, SQL="+aNameSQL);
						Util.printStack(ex);
					} 
				}
			} catch (Exception ex) {
				String message = code + ", namesSQL=" + internalID + " : " + ex.toString();
				log.error(message);
				this.getErrors().add(message);
				Util.printStack(ex);
			}
		}
	}
	if (notesSQL != null) {
		for (String aNoteSQL: notesSQL) {
			try {
				// create and execute a SELECT
				PreparedStatement aStmt = SqlSource.prepareQueryDB(con,aNoteSQL);
				ResultSet rs = SqlSource.queryDB(aNoteSQL, aStmt,internalID);
				if (rs != null) {
					while (rs.next()) {
						Status currStatus = null;
						try {
							String aStatus = rs.getString("status");
							if (aStatus != null && aStatus.length() > 0) {
								// log.debug(nameLang+"="+aName);
								currStatus = Status.valueOf(aStatus);
							}
						} catch (java.sql.SQLException ex) {
						}
						TermFormat currFormat = null;
						try {
							String aFormat = rs.getString("format");
							if (aFormat != null && aFormat.length() > 0) {
								// log.debug(nameLang+"="+aName);
								currFormat = TermFormat.valueOf(aFormat);
							}
						} catch (java.sql.SQLException ex) { // a column may
							// be missing
							// for a given
							// language
						}
						// get current row values
						String currLang = TermList.anyLanguage;
						try {
							String aLang = rs.getString("lang");
							if (aLang != null && aLang.length() > 0) {
								// log.debug(nameLang+"="+aName);
								currLang = aLang;
							}
						} catch (java.sql.SQLException ex) {
						}
						try {
							String aNote = rs.getString("note");
							if (aNote != null && aNote.length() > 0) {
								// log.debug(nameLang+"="+aName);
								aConcept.putScopeNote(currLang, currFormat, aNote, currStatus);
							}
						} catch (java.sql.SQLException ex) {
						}
						for (Term nameLang : this.getTitle()) {
							Status currLStatus = currStatus;
							try {
								String aStatus = rs.getString("status-" + nameLang.getLang());
								if (aStatus != null && aStatus.length() > 0) {
									// log.debug(nameLang+"="+aName);
									currLStatus = Status.valueOf(aStatus);
								}
							} catch (java.sql.SQLException ex) {
							}
							TermFormat currLFormat = currFormat;
							try {
								String aFormat = rs.getString("format-" + nameLang.getLang());
								if (aFormat != null && aFormat.length() > 0) {
									// log.debug(nameLang+"="+aName);
									currLFormat = TermFormat.valueOf(aFormat);
								}
							} catch (java.sql.SQLException ex) {
							}
							try {
								String aNote = rs.getString("note-" + nameLang.getLang());
								if (aNote != null && aNote.length() > 0) {
									// log.debug(nameLang+"="+aName);
									aConcept.putScopeNote(nameLang.getLang(), currLFormat, aNote, currLStatus);
								}
							} catch (java.sql.SQLException ex) {
							}
						}
					}
					log.debug(code + ", notesSQL=" + internalID + ":"+aConcept.getScopeNote(""));
					rs.close();
					//notesStmt.close();
				}
				if (aStmt != null) {
					try {
						aStmt.close();
					} catch (SQLException ex) {
						log.error(ex.toString()+", closing statement set, SQL="+aNoteSQL);
						Util.printStack(ex);
					} 
				}
			} catch (Exception ex) {
				String message = code + ", notesSQL=" + internalID + " : " + ex.toString();
				log.error(message);
				this.getErrors().add(message);
				Util.printStack(ex);
			}
		}
	}
	if (aliassesSQL != null) {
		for (String aliassSQL: aliassesSQL) {
			try {
				// create and execute a SELECT
				PreparedStatement aStmt = SqlSource.prepareQueryDB(con,aliassSQL);
				ResultSet rs = SqlSource.queryDB(aliassSQL, aStmt,internalID);
				if (rs != null) {
					while (rs.next()) {
						Status currStatus = null;
						try {
							String aStatus = rs.getString("status");
							if (aStatus != null && aStatus.length() > 0) {
								// log.debug(nameLang+"="+aName);
								currStatus = Status.valueOf(aStatus);
							}
						} catch (java.sql.SQLException ex) {
						}
						String currOrigin = rs.getString("alias");
						if (currOrigin == null || currOrigin.length() == 0) {
							String message = code + ": alias field is not returned by SQL query for aliasses";
							log.error(message);
							this.getErrors().add(message);
							break;
						}
						if (currOrigin.startsWith(code+SchemeInterface.separator))
							currOrigin = currOrigin.substring(code.length()+1);
						this.putAlias(currOrigin, currCode);
						aConcept.putAlias(currOrigin, currStatus);
					}
					rs.close();
					//aliassesStmt.close();
				}
				if (aStmt != null) {
					try {
						aStmt.close();
					} catch (SQLException ex) {
						log.error(ex.toString()+", closing statement set, SQL="+aliassSQL);
						Util.printStack(ex);
					} 
				}
			} catch (Exception ex) {
				String message = code + ", aliassesSQL=" + internalID + " : " + ex.toString();
				log.error(message);
				this.getErrors().add(message);
				Util.printStack(ex);
			}
		}
	}
	if (synonymsSQL != null) {
		for (String synonymSQL: synonymsSQL) {
			try {
				// create and execute a SELECT
				PreparedStatement aStmt = SqlSource.prepareQueryDB(con,synonymSQL);
				ResultSet rs = SqlSource.queryDB(synonymSQL, aStmt,internalID);
				if (rs != null) {
					while (rs.next()) {
						Status currStatus = null;
						try {
							String aStatus = rs.getString("status");
							if (aStatus != null && aStatus.length() > 0) {
								// log.debug(nameLang+"="+aName);
								currStatus = Status.valueOf(aStatus);
							}
						} catch (java.sql.SQLException ex) {
						}
						TermFormat currFormat = null;
						try {
							String aFormat = rs.getString("format");
							if (aFormat != null && aFormat.length() > 0) {
								// log.debug(nameLang+"="+aName);
								currFormat = TermFormat.valueOf(aFormat);
							}
						} catch (java.sql.SQLException ex) { // a column may
							// be missing
							// for a given
							// language
						}
						String currLanguage = TermList.anyLanguage;
						try {
							currLanguage = rs.getString("lang");
							if (currLanguage == null || currLanguage.length() == 0)
								currLanguage = TermList.anyLanguage;
						} catch (java.sql.SQLException ex) {
						}
						String currSource = null;
						try {
							currSource = rs.getString("source");
						} catch (java.sql.SQLException ex) {
						}
						String currSynonym = rs.getString("alternate");
						if (currSynonym == null) {
							String message = code + ": alternate field is not returned by SQL query for alternate labels";
							log.error(message);
							this.getErrors().add(message);
							break;
						}
						if (currSynonym.length() > 0)
							aConcept.putAltLabel(currLanguage, currFormat, currSynonym, currSource, currStatus);
					}
					rs.close();
				}
				//synonymsStmt.close();
				if (aStmt != null) {
					try {
						aStmt.close();
					} catch (SQLException ex) {
						log.error(ex.toString()+", closing statement set, SQL="+synonymSQL);
						Util.printStack(ex);
					} 
				}
			} catch (Exception ex) {
				String message = code + ", synonymsSQL=" + internalID + " : " + ex.toString();
				log.error(message);
				this.getErrors().add(message);
				Util.printStack(ex);
			}
		}
	}
	
	
	if (!everything) {
		return;
	}
	
	try {
		readRelations(con, aConcept, internalID, parentsSQL, true,"broader",Concept.class.getMethod("putBroader",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("broader: unknown relation");
	}
	try {
		readRelations(con, aConcept, internalID, childrenSQL, true,"narrower",Concept.class.getMethod("putNarrower",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("broader: unknown relation");
	}
	try {
		readRelations(con, aConcept, internalID, relatedSQL, true,"related",Concept.class.getMethod("putRelated",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("related: unknown relation");
	}
	try {
		readRelations(con, aConcept, internalID, broadMatchSQL, false,"broadmatch",Concept.class.getMethod("putBroadMatch",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("broadmatch: unknown relation");
	}
	try {
		readRelations(con, aConcept, internalID, narrowMatchSQL, false,"narrowmatch",Concept.class.getMethod("putNarrowMatch",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("broadmatch: unknown relation");
	}
	try {
		readRelations(con, aConcept, internalID, relatedMatchSQL, false,"relatedmatch",Concept.class.getMethod("putRelatedMatch",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("relatedmatch: unknown relation");
	}
	try {
		readRelations(con, aConcept, internalID, exactMatchSQL, false,"exactmatch",Concept.class.getMethod("putExactMatch",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("exactmatch: unknown relation");
	}
	try {
		readRelations(con, aConcept, internalID, closeMatchSQL, false,"closematch",Concept.class.getMethod("putCloseMatch",String.class,Status.class,String.class));
	} catch (NoSuchMethodException NSM) {
		log.error("closematch: unknown relation");
	}

	if (indexSQL.size() > 0) {
		for (String indexCode : indexSQL.keySet()) {
			NotationScheme subList = (NotationScheme) skosManager.getScheme(indexCode);
			String[] sqlQueries = indexSQL.get(indexCode);
			if (sqlQueries != null) {
				for (String sqlQuery: sqlQueries) {
					try {
						PreparedStatement aStmt = SqlSource.prepareQueryDB(con,sqlQuery);
						ResultSet rs = SqlSource.queryDB(sqlQuery, aStmt,internalID);
						if (rs != null) {
							while (rs.next()) {
								String currIndex = rs.getString("notation");
								if (currIndex == null) {
									String message = code + ": notation field is not returned by SQL query for notation "+indexCode;
									log.error(message);
									this.getErrors().add(message);
									break;
								}
								if (currIndex.startsWith(code+SchemeInterface.separator))
									currIndex = currIndex.substring(code.length()+1);
								if (currIndex.length() > 0) {
									aConcept.putNotation(indexCode,currIndex);
								}
							}
							rs.close();
						}
						//aStmt.close();
						if (aStmt != null) {
							try {
								aStmt.close();
							} catch (SQLException ex) {
								log.error(ex.toString()+", closing statement set, SQL="+indexSQL);
								Util.printStack(ex);
							} 
						}
					} catch (Exception ex) {
						String message = code + ", indexSQL="+indexCode+": " + ex.toString();
						log.error(message);
						this.getErrors().add(message);
						Util.printStack(ex);
					}
				}
			}
		}
	}
	CountCacher.retrieveAll(aConcept);
}

private LinkedList<Concept> allConceptsSQL() {
	Connection con = null;

	con = poolContext.getConnection();
	SkosManager skosManager = this.getManagerInstance();
	LinkedList<Concept> result = null;
	if (allSQL != null && skosManager != null) {
		String code = this.getAbout();
		for (String someSQL: allSQL) {
			try {
				// create and execute a SELECT
				PreparedStatement aStmt = SqlSource.prepareQueryDB(con,someSQL);
				ResultSet rs = SqlSource.queryDB(someSQL, aStmt,"");
				if (rs != null) {
					result = new LinkedList<Concept>();
					while (rs.next()) {
						String currCode = SqlSource.getAbout(this,rs,"about", true);
						String internalID = null;
						if (currCode == null || currCode.length() == 0) {
							String message = "about field is not returned by the SQL query to get all concepts";
							log.error(message);
							this.getErrors().add(message);
						}
						else {
							internalID = currCode;
						}

						try {
							String currId = rs.getString("id"); // Because it may ease the next step to use an internal ID
							if (currId != null && currId.length() > 0) {
								internalID = currId;
							}
						} catch (SQLException e) { }
						Concept aConcept = new Concept(this);
						aConcept.setAbout(currCode);
						result.add(aConcept);
						readLabels(rs,aConcept); // Labels may be available with the find
						log.debug("get all Scheme="+code + ", read about=" + currCode + ", id="+internalID);
						oneConceptSQL(con,aConcept, currCode, internalID,false);
					}
					rs.close();
					//findStmt.close();
				}
				if (aStmt != null) {
					try {
						aStmt.close();
					} catch (SQLException ex) {
						log.error(ex.toString()+", closing statement set, SQL="+allSQL);
						Util.printStack(ex);
					} 
				}
			} catch (Exception ex) {
				String message = "get all Scheme="+code + " : " + ex.toString();
				log.error(message);
				this.getErrors().add(message);
				Util.printStack(ex);
			}
		}
	}
	SqlPool.freeConnection(con);
	return result;
}
	/**
	 * @return the concepts for an handle (redirected or not). Null if none
	 */
public synchronized Concept getConcept(String handle) {
	if (handle == null) return null;
	handle = SkosUtil.normalizeAbout(handle);
	if (handle.length()==0) return null;
	synchronized(this) {
		if (this.entries.containsKey(handle))
			return this.entries.get(handle);
		if (this.aliasses.containsKey(handle)) {
			String aKey = this.aliasses.get(handle);
			return this.entries.get(aKey);
		}
		Connection con = poolContext.getConnection();
		Concept aConcept = readSQL(con, handle, true);
		// close statement
		SqlPool.freeConnection(con);
		return aConcept;
	}
}

/**
 * @return the concepts for an handle (redirected or not). Null if none
 */
public synchronized Concept getConcept(Connection con, String handle) {
	if (handle == null) return null;
	handle = SkosUtil.normalizeAbout(handle);
	if (handle.length()==0) return null;
	synchronized(this) {
		if (this.entries.containsKey(handle))
			return this.entries.get(handle);
		if (this.aliasses.containsKey(handle)) {
			String aKey = this.aliasses.get(handle);
			return this.entries.get(aKey);
		}
		return readSQL(con, handle, true);
	}
}

/**
 * @return the concepts for an handle (redirected or not). Null if none
 */
public synchronized Concept getUncachedEssential(Connection con, String handle) {
	if (handle == null) return null;
	handle = SkosUtil.normalizeAbout(handle);
	if (handle.length()==0) return null;
	synchronized(this) {
		if (this.entries.containsKey(handle))
			return this.entries.get(handle);
		if (this.aliasses.containsKey(handle)) {
			String aKey = this.aliasses.get(handle);
			return this.entries.get(aKey);
		}
		return readSQL(con, handle, false);
	}
}

public ArrayList<SearchResult> labelMatch(String prefix, String userLanguage, boolean strictLanguage, LabelMatchType matchType) {
	return this.labelMatch(prefix,userLanguage,strictLanguage,matchType, null);
}

public ArrayList<SearchResult> labelMatch(String prefix, String userLanguage, boolean strictLanguage, LabelMatchType matchType, Collection<Concept> filter) {
	log.debug("DynamicMatch=" + prefix + ", " + userLanguage + ", " + matchType);

	//SINGULAR_WORDS,
	//SINGULAR_SENTENCES
	String code = this.getAbout();
	if (prefix == null || (prefix.length() == 0))
		return new ArrayList<SearchResult>();
	if (matchType == LabelMatchType.INDEPENDANT_PIECES) {
		StringTokenizer st = new StringTokenizer(prefix,""+SchemeInterface.pieceBoundary);
		ArrayList<SearchResult>matchResult = new ArrayList<SearchResult>();
		ArrayList<SearchResult> prevMatchResult = null;
		if (filter != null) {
			prevMatchResult = new ArrayList<SearchResult>();
			for (Concept aConcept:filter) {
				prevMatchResult.add(new SearchResult("", "", "", "", aConcept.getScheme_About(), 1.0f));
			}
		}
		while (st.hasMoreTokens()) {
			String aPrefix = st.nextToken();
			matchResult = labelMatch(aPrefix, userLanguage, strictLanguage, LabelMatchType.ANYWHERE );
			log.debug("Matches="+matchResult);
			if (matchResult == null || matchResult.isEmpty()) {
				return new ArrayList<SearchResult>();
			} else {
				if (prevMatchResult == null) {
					prevMatchResult = matchResult;					
				} else {
					prevMatchResult.retainAll(matchResult);
				}
			}
		}
		return prevMatchResult;
	} else {
		if ( matchSQL == null || matchSQL.length==0 ) return new ArrayList<SearchResult>();
		HashSet<String> filterAbout = null;
		if (filter != null) {
			filterAbout = new HashSet<String>();
			for (Concept aConcept:filter) {
				filterAbout.add(aConcept.getScheme_About());
			}
		}
		String prefixQuery = "";
		char goodJoker = sqlJoker;
		if (matchSQLfull) goodJoker = SchemeInterface.joker_0_n;
		else {
			prefixQuery = Util.replaceAllNoRegExp(prefixQuery, ""+SchemeInterface.joker_0_n, ""+sqlJoker);			
		}
		LinkedList<String> words;
		// LabelMatchType: EXACT, STARTS_WITH, ANYWHERE, EVERYTHING
		switch (matchType) {
		case EVERYTHING: 
			prefixQuery = ""+goodJoker;
			// NOT POSSIBLE WITH FULLTEXT MATCH?
			break;
		case ANYWHERE: 
			if (matchSQLfull) {
				words = Util.wordSplitter(prefix);
				if (words == null || words.isEmpty()) return new ArrayList<SearchResult>();
				prefixQuery = null;
				for (String word : words) {
					prefixQuery = (prefixQuery == null ? "" : prefixQuery+' ') + '+' + word + goodJoker;
				}
			} else prefixQuery = sqlJoker+prefix+sqlJoker;
			break;
		case STARTS_WITH: 
			if (matchSQLfull) {
				prefixQuery = '"'+prefix+'"'+goodJoker;				
			}
			else {
				prefixQuery = prefix+goodJoker;
			}
			break;
		case EXACT: 
			if (matchSQLfull) {
				prefixQuery = '"'+prefix+'"';
			} else prefixQuery = prefix;
			break;
		case SINGULAR_WORDS:
			words = Util.singularWordSplitter(prefix);
			if (words == null || words.isEmpty()) return new ArrayList<SearchResult>();
			prefixQuery = null;
			for (String word : words) {
				prefixQuery = (prefixQuery == null ? "" : prefixQuery+' ') + (matchSQLfull?"+":"") + word + goodJoker;
			}
			break;
		case SINGULAR_SENTENCES:
			//TODO: NOT IMPLEMENTED YET!!!!
			words = Util.sentenceSplitter(prefix);
			if (words == null || words.isEmpty()) return new ArrayList<SearchResult>();
			prefixQuery = null;
			for (String word : words) {
				prefixQuery = (prefixQuery == null ? "" : prefixQuery+' ') + word + goodJoker;
			}
			break;
		}
		Connection con = poolContext.getConnection();
		TreeMap<String,SearchResult> result2 = new TreeMap<String,SearchResult>();
		for (String aMatchSQL : matchSQL) {
			try {
				TreeMap<String,SearchResult> result = new TreeMap<String,SearchResult>();
				// create and execute a SELECT
				PreparedStatement aStmt = SqlSource.prepareQueryDB(con,aMatchSQL);
				ResultSet rs = SqlSource.queryDB(aMatchSQL, aStmt,prefixQuery);
				//matchStmt.setString(2, userLanguage);
				if (rs != null) {
					while (rs.next()) {
						String currCode = SqlSource.getAbout(this,rs,"about", true);
						if (currCode == null || currCode.length() == 0) {
							String message = code + ": about field is not returned by SQL query for label match";
							log.error(message);
							this.getErrors().add(message);
							continue;
						}
						if (currCode.startsWith(code+SchemeInterface.separator))
							currCode = currCode.substring(code.length()+1);
						String currSchemeCode = this.getAbout_About(currCode);
						if (filterAbout != null && !filterAbout.contains(currSchemeCode)) {
							continue; // Not in the filter: no time to loose!
						}
						String currMatch = "";
						try {
							currMatch = rs.getString("match");
							if (currMatch == null) currMatch = "";
						} catch (SQLException e) { 
						}

						String currLanguage = TermList.anyLanguage;
						try {
							currLanguage = rs.getString("lang");
							if (currLanguage == null) currLanguage = TermList.anyLanguage;
						} catch (SQLException e) { 
						}
						/*
					if ( (currMatch.length() > 0)
							&& !( TermList.anyLanguage.equalsIgnoreCase(currLanguage)
									|| userLanguage.equalsIgnoreCase(currLanguage) ) )
						currMatch = '('+currLanguage+')'+currMatch;
						 */
						String currKey = currCode;
						SearchResult toStore = new SearchResult(null,currMatch,currLanguage,null,currSchemeCode,1.0f);
						// For a given about, keep the shortest match
						SearchResult currEntry = result.get(currKey);
						if (currEntry == null) result.put(currKey,toStore);
						else if (currEntry.getSynonym().length() > toStore.getSynonym().length()) result.put(currKey,toStore);
					}
					rs.close();
					if (aStmt != null) {
						try {
							aStmt.close();
						} catch (SQLException ex) {
							log.error(ex.toString()+", closing statement set, SQL="+aMatchSQL);
							Util.printStack(ex);
						} 
					}
				}
				//matchStmt.close();
				log.debug(result.size()+" result entries");
				for (Entry<String,SearchResult> resultEntry: result.entrySet()) {
					Concept aConcept = this.getUncachedEssential(con,resultEntry.getKey());
					if (aConcept != null) {
						String aLabel = aConcept.getPrefLabel(userLanguage, strictLanguage);
						if (aLabel != null) {
							SearchResult currEntry = resultEntry.getValue();
							currEntry.setLabel(aLabel);
							if (aLabel.equalsIgnoreCase(currEntry.getSynonym())) {
								currEntry.setSynonym(null);
							}
							String scopeNote = aConcept.getScopeNote(userLanguage);
							if (scopeNote == null || scopeNote.isEmpty()) {
								scopeNote = "";
							} else {
								currEntry.setNote(managerInstance.toStringScheme_Concept(scopeNote, userLanguage, this, null, null, true));
							}
							result2.put(aConcept.getSortLabel(userLanguage),currEntry);
						}
					}
				}
			} catch (Exception ex) {
				String message = code + ", matchSQL=" + prefix + " : " + ex.toString();
				log.error(message);
				this.getErrors().add(message);
				Util.printStack(ex);
			}
		}
		log.debug(result2.size()+" concepts");
		con = SqlPool.freeConnection(con);
		return new ArrayList<SearchResult>(result2.values());
	}
}

	/**
	 * @return the cacheSize
	 */
	@XmlTransient
	public synchronized int getCacheSize() {
		return cacheSize;
	}

	/**
	 * @param cacheSize the cacheSize to set
	 */
	public synchronized void setCacheSize(int cacheSize) {
		if (cacheSize > this.cacheSize) { // Cache can only grow!
			this.cacheSize = cacheSize;
			int hashTableCapacity = (int) Math.ceil(this.cacheSize
					/ hashTableLoadFactor) + 1;
			LinkedHashMap<String, Concept> newEntries = new LinkedHashMap<String, Concept>(hashTableCapacity,
					hashTableLoadFactor, true) {
				// An anonymous inner class to define a procedure which decides when cache is full
				private static final long serialVersionUID = 1;

				/*
				 * Manages the eldest entry when it has to be removed (cache full)
				 */
				protected boolean removeEldestEntry(Map.Entry<String,Concept> eldest) {
					if (size() > DynamicScheme.this.cacheSize) {
						Concept eldestConcept = eldest.getValue();
						if (eldestConcept != null)
							removeMappings(eldestConcept);
						return true;
					}
					else return false;
				}
			};
			if (this.entries != null) {
				newEntries.putAll(this.entries);
			}
			this.entries = newEntries;
		};
	}

	/*
	 * Ensures a new cache is set up
	 */
	public synchronized void setRenewedCacheSize(int cacheSize) {
		this.entries = null;
		this.cacheSize = -1;
		this.setCacheSize(cacheSize);
	}

	/**
	 * @return ALL the concepts: this effectively load them ALL in memory
	 */
	@XmlTransient
	public synchronized LinkedList<Concept> getConcept() {
		if (this.noConcepts) return null;
		Thread.dumpStack();
		return allConceptsSQL(); // TODO: load all concepts!!!
	}

	@XmlTransient
	public synchronized LinkedList<Concept> getAllConcepts() {
		Thread.dumpStack();
		return allConceptsSQL(); // TODO: load all concepts!!!
	}

	/**
	 * @return the concepts in the memory cache
	 */
	@XmlTransient
	public synchronized LinkedList<Concept> getConceptInMemory() {
		return new LinkedList<Concept>(this.entries.values()); // Returns only Cache content: THIS PRECLUDES SERIALIZATION OF DynamicScheme
	}
/*
	public LinkedHashMap<String,ReferringApplication> getConceptReferringApplication(String application) {
		LinkedHashMap<String,ReferringApplication> result = new LinkedHashMap<String,ReferringApplication>();
		for (Entry<String,ReferringApplicationList> entry: countCache.entrySet() ) {
			if (entry.getKey() != null) {
				ReferringApplication aList = entry.getValue().getReferringApplication(application);
				if (aList != null) result.put(entry.getKey(),aList);
			}
		}
		return result;
	}
*/
	protected void mapAlias() {
		// Already managed in an SQL statement 
	}

	protected void mapIndexLabel() {
		// An SQL statement is providing the necessary search by a label
		// TODO: use an SQL statement to find duplicates
	}

	protected void ensureNarrower() {
		// DONE: a SQL transaction to get the narrowers of a given concept.
		// TODO: a SQL transaction should calculate the depth of a given concept (not easy if not stored in the database)
		// TODO: a SQL transaction to calculate the maximal depth in the scheme (not easy if not stored in the database)
		// TODO: a SQL transaction to get all top concepts of the Scheme
		this.hierarchized = false;
	}

	protected void ensureRelatedSymetry() {
		// The SQL transaction to get Related must provide automatically the local reciprocals...
		// DONE: Reciprocals are supposed to be complete in the database itself (UNION in the SQL if necessary)
	}

	protected void findMatchedSchemes() {
		// TODO: An SQL transaction should take the "match" relations and list all the "about" of the schemes involved
	}

	public synchronized void preLoadFinished() {
			this.mapIndexLabel();
			this.mapAlias();
			this.ensureNarrower();
			this.ensureRelatedSymetry();
			this.findMatchedSchemes();
	}

	/**
	 * @return the concepts
	 */
	@XmlTransient
	public synchronized Map<String,Concept> getConceptMap() {
		return entries; // Returns only cache content: THIS PRECLUDES SERIALIZATION OF DynamicScheme
	}

/*
	@XmlTransient
	public LinkedList<Concept> getHasTopConcept() {
		LinkedList<Concept> tops = new LinkedList<Concept>();
		//TODO: SQL Request to search all top concepts!!!
		for (Concept aConcept: entries.values()) {
			if (aConcept.isTopConcept()) tops.add(aConcept);
		}
		return tops;
	}
*/
	/**
	 * @return the aliasses
	 */
	@XmlTransient
	public Map<String, String> getAlias() {
		if (this.aliasses == null) this.aliasses = new ConcurrentHashMap<String,String>();
		return aliasses; // Returns only cache content: THIS PRECLUDES SERIALIZATION OF DynamicScheme
	}

	/**
	 * @param handle of the entry to put amongst concepts
	 * @param entry to put amongst concepts
	 */
	public synchronized void removeConcept(Concept entry) {
		removeMappings(entry);
		this.entries.remove(entry);
	}

	/**
	 * @param entry to put amongst concepts
	 */
	public synchronized Concept putConcept(Concept entry) {
		String aCode = entry.getAbout();
		if (aCode == null) {
			String message = "ConceptScheme cannot receive concepts without code";
			log.error(message);
			this.getErrors().add(message);
		}
		entry.setInScheme(this);
		Concept oldValue = this.entries.put(aCode, entry);
		if (oldValue != null && oldValue != entry) { // Replace a different entry
			removeMappings(oldValue);
		} // else entry already there!
		return entry;
	}

	/**
	 * @param conceptMap the concepts to set
	 */
	public synchronized void setConcept(List<Concept> someEntries) {
		    int curSize = this.cacheSize;
		    this.cacheSize = 0;
		    for (Concept aConcept: entries.values()) {
		    	removeMappings(aConcept);
		    }
		    this.setCacheSize(curSize >= someEntries.size()? curSize : someEntries.size());
			for (Concept aConcept: someEntries) {
				putConcept(aConcept);
			}
	}

	/**
	 * @return the schemeSize
	 */
	@XmlTransient
	public int getSize() {
		return schemeSize;
	}

	/**
	 * @param schemeSize the schemeSize to set
	 */
	public void setSize(int schemeSize) {
		this.schemeSize = schemeSize;
	}
	
	public Concept getNotationConcept (String indexCode, String notationCode) {
		if (indexSearchSQL.size() <= 0) return null;
		String currAbout = "";
		Connection con = poolContext.getConnection();
		String code = this.getAbout();
		//			NotationScheme subList = (NotationScheme) SkosManager.getScheme(indexCode);
		String[] sqlQueries = indexSearchSQL.get(indexCode);
		if (sqlQueries != null) {
			for (String sqlQuery: sqlQueries) {
				try {
					log.debug(notationCode+" is parameter in "+sqlQuery);
					PreparedStatement aStmt = SqlSource.prepareQueryDB(con,sqlQuery);
					ResultSet rs = SqlSource.queryDB(sqlQuery, aStmt, notationCode);
					if (rs != null) {
						if (rs.next()) {
							currAbout = SqlSource.getAbout(this,rs,"about", true);
						}
						rs.close();
					}
					if (aStmt != null) {
						try {
							aStmt.close();
						} catch (SQLException ex) {
							log.error(ex.toString()+", closing statement set, SQL="+indexSearchSQL.get(indexCode));
							Util.printStack(ex);
						} 
					}
				} catch (Exception ex) {
					String message = code + ", indexSQL="+indexCode+": " + ex.toString();
					log.error(message);
					this.getErrors().add(message);
					Util.printStack(ex);
				}
			}
		}
		if (currAbout == null || currAbout.length() == 0) {
			// Error in SQL or notation does not exist???
			String message = code + ": about field is not returned by SQL Find query for notation "+indexCode+" or notation code \""+notationCode+"\" does not exist.";
			log.error(message);
			this.getErrors().add(message);
			SqlPool.freeConnection(con);
			return null;
		}
		Concept aConcept = getConcept(con, currAbout); // This will make all the readSQL work!
		//				subList.putConcept(handle, aConcept);
		//				aConcept.putNotation(indexCode,currAbout);
		SqlPool.freeConnection(con);
		return aConcept;
	}
/*	
	public ReferringApplicationList getConceptUsage (String about) {
		ReferringApplicationList usage = countCache.get(about);
		if (usage == null) {
			usage = new ReferringApplicationList();
			countCache.put(about, usage);
		}
		return usage;
	}
*/
	public boolean equals(Object o) {
		return super.equals(o);
	}

	protected void complete(Concept aConcept) {
		if (aConcept != null) {
			if (!aConcept.isComplete()) {
				aConcept.setComplete(true); // must be done FIRST to ensure no recursive call to complete !!!
				String currCode = aConcept.getAbout();
				this.putConcept(currCode, aConcept);
				Connection con = poolContext.getConnection();				
				oneConceptSQL(con, aConcept, currCode, currCode, true);		
				SqlPool.freeConnection(con);
			}
		}
	}
}
